﻿using System;
using Notoric.Model.Elements.CWNotation;
using Notoric.Model.Packaging;

namespace Notoric.Model.Elements.Internal
{
    /// <summary>
    /// Common base class for border requests data classes.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Border requests are used uniquely in the scores to handle cases
    /// where a situation occurs that has an effect on a logical neighbour
    /// element of the same type. But this neighbour is located somewhere
    /// in the score (e.g. a <see cref="CWNotation.Staff"/> in the last 
    /// <see cref="CWNotation.System"/> of a <see cref="MusicSectionElement"/>
    /// needs to inform the other staff instance that has the same 
    /// <see cref="CWNotation.StaffDescriptor"/> that the <see cref="CWNotation.Environment"/>
    /// has changed, but the other staff is in another music section instance
    /// possibly on a different page).<br/>
    /// The elements does not have references to their logical neighbours because
    /// their management would be relatively highly complex. Instead, the element
    /// emits a call to a <i>ProcessBorderRequest</i> method of its <see cref="ModelElement.Owner"/>
    /// and the owner will attempt to find the neighbour within its scope.
    /// If it fails, it emits the calls the <i>ProcessBorderRequest</i> to its owner
    /// and this process is repeated until the neighbour is found or the root score is reached.
    /// </para>
    /// <para>
    /// When the neighbour is found, the request redescends to its target using the <i>HandleBorderRequest</i>
    /// method calls. If the neighbour is not found, then the action depends on the request.
    /// Typically, the request is either silently ignored (such as in case of staff's environment change)
    /// or the neighbour element is created (as for example in case of overflowing measures).
    /// </para>
    /// </remarks>
    internal abstract class BorderRequest
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="BorderRequest"/> instance.
        /// </summary>
        /// <param name="request">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Identifies the type of the request.
        /// </para>
        /// </param>
        /// <param name="requestDirection">
        /// <para>
        /// Type: <see cref="Direction"/>
        /// </para>
        /// <para>
        /// The direction of the request.
        /// </para>
        /// </param>
        protected BorderRequest(int request, Direction requestDirection)
        {
            Request = request;
            RequestDirection = requestDirection;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the value that identifies the type of the request.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Identifies the type of the request.
        /// </para>
        /// </value>
        public int Request
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the direction of the request.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Direction"/>
        /// </para>
        /// <para>
        /// The direction of the request.
        /// </para>
        /// </value>
        public Direction RequestDirection
        {
            get;
            set;
        }

        #endregion

        #region Public Constants

        /// <summary>
        /// Identifies a "Process Inner Request" request type.
        /// </summary>
        /// <remarks>
        /// This type is used when this request was created as a response to another
        /// border request coming from a child element in a case where its neighbour
        /// could not be found in the current scope.
        /// </remarks>
        public const int PROCESS_INNER_REQUEST = 0;

        #endregion
    }

    /// <summary>
    /// Identifies the direction of a border request.
    /// </summary>
    internal enum Direction
    {
        /// <summary>
        /// The request is propagated to the left logical neighbour.
        /// </summary>
        Left,
        /// <summary>
        /// The request is propagated to th right logical neighbour.
        /// </summary>
        Right
    }

    /// <summary>
    /// Holds the data of a border request that was emitted by a <see cref="CWNotation.Staff"/>.
    /// </summary>
    internal class StaffBorderRequest : BorderRequest
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="StaffBorderRequest"/> instance.
        /// </summary>
        /// <param name="request">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Identifies the type of the request.
        /// </para>
        /// </param>
        /// <param name="requestDirection">
        /// <para>
        /// Type: <see cref="Direction"/>
        /// </para>
        /// <para>
        /// The direction in which the logical neighbour is searched.
        /// </para>
        /// </param>
        /// <param name="descriptor">
        /// <para>
        /// Type: <see cref="StaffDescriptor"/>
        /// </para>
        /// <para>
        /// The descriptor of the staff that emitted the request.
        /// </para>
        /// </param>
        public StaffBorderRequest(int request, Direction requestDirection, StaffDescriptor descriptor)
            : base(request, requestDirection)
        {
            Descriptor = descriptor;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the descriptor of the staff that emitted the request.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="StaffDescriptor"/>
        /// </para>
        /// <para>
        /// The descriptor of the staff that has initiated the request.
        /// </para>
        /// </value>
        public StaffDescriptor Descriptor
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the environment on the staff border.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="CWNotation.Environment"/>
        /// </para>
        /// <para>
        /// The current environment on the corresponding border of the staff.<br/>
        /// </para>
        /// </value>
        public CWNotation.Environment BorderEnvironment
        {
            get;
            set;
        }

        #endregion

        #region Public Constants

        /// <summary>
        /// Identifies an "Environment Changed" border request.
        /// </summary>
        /// <remarks>
        /// This request is used when the environment on a staff border has changed
        /// and the staff wants to inform its logical neighbour.
        /// </remarks>
        public const int ENVIRONMENT_CHANGED = 1;
        /// <summary>
        /// Identifies a "Pull Environment" border request.
        /// </summary>
        /// <remarks>
        /// This request is used when a new staff instance is created and it needs
        /// to know the environment of its logical neighbour.
        /// </remarks>
        public const int PULL_ENVIRONMENT = 2;

        #endregion
    }

    /// <summary>
    /// Abstract base class for border requests coming from the root element of a <see cref="SectionInteriorElement"/>.
    /// </summary>
    internal abstract class InteriorRootBorderRequest : BorderRequest
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="InteriorRootBorderRequest"/> instance.
        /// </summary>
        /// <param name="request">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Identifies the type of the request.
        /// </para>
        /// </param>
        /// <param name="requestDirection">
        /// <para>
        /// Type: <see cref="Direction"/>
        /// </para>
        /// <para>
        /// The direction in which the logical neighbour is searched.
        /// </para>
        /// </param>
        protected InteriorRootBorderRequest(int request, Direction requestDirection)
            : base(request, requestDirection)
        {
        }

        #endregion
    }

    /// <summary>
    /// Holds the data of a border request that was emitted by a <see cref="CWNotation.System"/>.
    /// </summary>
    internal class SystemBorderRequest : InteriorRootBorderRequest
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="SystemBorderRequest"/> instance.
        /// </summary>
        /// <param name="request">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Identifies the type of the request.
        /// </para>
        /// </param>
        /// <param name="requestDirection">
        /// <para>
        /// Type: <see cref="Direction"/>
        /// </para>
        /// <para>
        /// The direction in which the logical neighbour is searched.
        /// </para>
        /// </param>
        /// <param name="sender">
        /// <para>
        /// Type: <see cref="CWNotation.System"/>
        /// </para>
        /// <para>
        /// The system that has initiated the request.
        /// </para>
        /// </param>
        public SystemBorderRequest(int request, Direction requestDirection, CWNotation.System sender)
            : base(request, requestDirection)
        {
            Sender = sender;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the inner request data.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="StaffBorderRequest"/>
        /// </para>
        /// <para>
        /// The data of the inner border request.
        /// </para>
        /// </value>
        public StaffBorderRequest InnerRequest
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the system that has initiated the request.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="CWNotation.System"/>
        /// </para>
        /// <para>
        /// The system that has initiated the request.
        /// </para>
        /// </value>
        public CWNotation.System Sender
        {
            get;
            set;
        }

        #endregion
    }

    /// <summary>
    /// Abstract base class for border requests coming from a <see cref="SectionElement"/>.
    /// </summary>
    internal abstract class SectionBorderRequest : BorderRequest
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="MusicSectionBorderRequest"/> instance.
        /// </summary>
        /// <param name="request">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Identifies the type of the request.
        /// </para>
        /// </param>
        /// <param name="requestDirection">
        /// <para>
        /// Type: <see cref="Direction"/>
        /// </para>
        /// <para>
        /// The direction in which the logical neighbour is searched.
        /// </para>
        /// </param>
        /// <param name="sender">
        /// <para>
        /// Type: <see cref="MusicSectionElement"/>
        /// </para>
        /// <para>
        /// The section that has initiated the request.
        /// </para>
        /// </param>
        public SectionBorderRequest(int request, Direction requestDirection, SectionElement sender)
            : base(request, requestDirection)
        {
            Sender = sender;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the desired type of the logical neighbour's interior.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Type"/>
        /// </para>
        /// <para>
        /// The desired type of the logical neighbour's interior.
        /// If this parameter should be ignored during the search, set it to <see langword="null"/> (default).
        /// </para>
        /// </value>
        public Type InteriorType
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the data of the inner request.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="InteriorRootBorderRequest"/>
        /// </para>
        /// <para>
        /// The data of the inner request.
        /// </para>
        /// </value>
        public InteriorRootBorderRequest InnerRequest
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the section that has initiated the request.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="SectionElement"/>
        /// </para>
        /// <para>
        /// The section that has initiated the request.
        /// </para>
        /// </value>
        public SectionElement Sender
        {
            get;
            set;
        }

        #endregion
    }

    /// <summary>
    /// Holds the data of a border request that was emitted by an <see cref="ImageSectionElement"/>.
    /// </summary>
    internal class ImageSectionBorderRequest : SectionBorderRequest
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="ImageSectionBorderRequest"/> instance.
        /// </summary>
        /// <param name="request">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Identifies the type of the request.
        /// </para>
        /// </param>
        /// <param name="requestDirection">
        /// <para>
        /// Type: <see cref="Direction"/>
        /// </para>
        /// <para>
        /// The direction in which the logical neighbour is searched.
        /// </para>
        /// </param>
        /// <param name="sender">
        /// <para>
        /// Type: <see cref="ImageSectionElement"/>
        /// </para>
        /// <para>
        /// The section that has initiated the request.
        /// </para>
        /// </param>
        public ImageSectionBorderRequest(int request, Direction requestDirection, ImageSectionElement sender)
            : base(request, requestDirection, sender)
        {
            
        }

        #endregion
    }

    /// <summary>
    /// Holds the data of a border request that was emitted by a <see cref="TextSectionElement"/>.
    /// </summary>
    internal class TextSectionBorderRequest : SectionBorderRequest
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="TextSectionBorderRequest"/> instance.
        /// </summary>
        /// <param name="request">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Identifies the type of the request.
        /// </para>
        /// </param>
        /// <param name="requestDirection">
        /// <para>
        /// Type: <see cref="Direction"/>
        /// </para>
        /// <para>
        /// The direction in which the logical neighbour is searched.
        /// </para>
        /// </param>
        /// <param name="sender">
        /// <para>
        /// Type: <see cref="ImageSectionElement"/>
        /// </para>
        /// <para>
        /// The section that has initiated the request.
        /// </para>
        /// </param>
        public TextSectionBorderRequest(int request, Direction requestDirection, TextSectionElement sender)
            : base(request, requestDirection, sender)
        {

        }

        #endregion
    }

    /// <summary>
    /// Holds the data of a border request that was emitted by a <see cref="MusicSectionElement"/>.
    /// </summary>
    internal class MusicSectionBorderRequest : SectionBorderRequest
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="MusicSectionBorderRequest"/> instance.
        /// </summary>
        /// <param name="request">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Identifies the type of the request.
        /// </para>
        /// </param>
        /// <param name="requestDirection">
        /// <para>
        /// Type: <see cref="Direction"/>
        /// </para>
        /// <para>
        /// The direction in which the logical neighbour is searched.
        /// </para>
        /// </param>
        /// <param name="sender">
        /// <para>
        /// Type: <see cref="MusicSectionElement"/>
        /// </para>
        /// <para>
        /// The section that has initiated the request.
        /// </para>
        /// </param>
        public MusicSectionBorderRequest(int request, Direction requestDirection, MusicSectionElement sender)
            : base(request, requestDirection, sender)
        {
            
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the number of the measure that the logical neighbour should contain.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The number of the measure that the logical neighbour should contain.
        /// If this parameter should be ignored during the search, set it to 0 (default).
        /// </para>
        /// </value>
        public ushort Measure
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the composition that the logical neighbour should present.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The composition that should be presented by the logical neighbour.
        /// If this parameter should be ignored during the search, set it to <see langword="null"/> (default).
        /// </para>
        /// </value>
        public CompositionPart PresentedComposition
        {
            get;
            set;
        }

        #endregion
    }

    /// <summary>
    /// Holds the data of a border request that was emitted by a <see cref="PageElement"/>.
    /// </summary>
    internal class PageBorderRequest : BorderRequest
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="PageBorderRequest"/> instance.
        /// </summary>
        /// <param name="request">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Identifies the type of the request.
        /// </para>
        /// </param>
        /// <param name="requestDirection">
        /// <para>
        /// Type: <see cref="Direction"/>
        /// </para>
        /// <para>
        /// The direction in which the logical neighbour is searched.
        /// </para>
        /// </param>
        /// <param name="sender">
        /// <para>
        /// Type: <see cref="PageElement"/>
        /// </para>
        /// <para>
        /// The page that has initiated the request.
        /// </para>
        /// </param>
        public PageBorderRequest(int request, Direction requestDirection, PageElement sender)
            : base(request, requestDirection)
        {
            Sender = sender;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the data of the inner request.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="SectionBorderRequest"/>
        /// </para>
        /// <para>
        /// The data of the inner request
        /// </para>
        /// </value>
        public SectionBorderRequest InnerRequest
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the page that has initiated the request.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="PageElement"/>
        /// </para>
        /// <para>
        /// The page that has initiated the request.
        /// </para>
        /// </value>
        public PageElement Sender
        {
            get;
            set;
        }

        #endregion
    }
}